#!/usr/bin/python
import itertools
import subprocess
import signal
import select
import sys
import os
import re

# Third-party modules
from configobj import configobj
from configobj import validate

# My modules
import misc
debugerror = misc._debug

def run(command, *args, **kwargs):
    """Run a command and return (return code, stdout, stderr)."""
    misc.debug(1, "Running command: %s" % command)
    return misc.run(command, *args, **kwargs)    
    
def mkdir(path):
    """Create a directory (only if it does not exist)."""
    if os.path.isdir(path):
        return
    os.mkdir(path)

def strip_tail(lst):
    for index, line in enumerate(lst[::-1]):
        if line.strip():
            return lst[:len(lst)-index]
    return lst

def strip_lines(lines):
    """Remove heading and trailing empty lines."""
    for index, line in enumerate(lines):
        if line.strip():
            break
    return strip_tail(list(lines[index:]))    
            
def str2re(s, metachars="\.^$*+?{[]|()"):
    """Escape special characters on regular-expressions"""     
    for c in metachars:
    	s = s.replace(c, "\\"+c)
    return s

def readlines(filename):
    """Get lines from filename. Return empty list if file does not exist."""
    if os.path.isfile(filename):
        return open(filename).readlines()
    return []

def getstring(obj):
    """Iterate and convert object to string (if it is not a string already).""" 
    if isinstance(obj, basestring):
        return obj
    return "\n".join(s.rstrip() for s in obj)+"\n"    
    
def write_lines(filename, dataseq):
    """Write lines on dataseq (adding line feed at the end) to filename"""
    file(filename, "w").writelines(s.rstrip()+"\n" for s in dataseq)
    
def overwrite_file(filename, new_contents):
    """Overwrite a file with some contents (sequence))"""
    try:
        contents = open(filename).read()
    except IOError:
        contents = None
    new_contents = getstring(new_contents)      
    modified = (contents != new_contents)
    if modified:
        open(filename, "w").write(new_contents)        
    return modified

def split_sections(lines, section_regexp):
    """Split sections on a list using a regular expression. Blank lines
    surrounding sections are yielded as separated sections (with key 'None')."""
    def _rsplit(lst, pred=bool):
        for n, item in enumerate(reversed(lst)):
            if pred(item):
                index = len(lst) - n 
                return lst[:index], lst[index:]
        return [], lst
    def _split(lst, section):    
        condition = lambda s: s.strip() and not s.lstrip().startswith("#")
        head, tail = _rsplit(lst, condition)
        yield (section, head)
        if tail:
            yield (None, tail)
    def _group(it, condition):    
        output = []
        section = None
        for element in it:
            new_section = condition(element)
            if not new_section:
                output.append(element)
                continue
            else:
                for item in _split(output, section):
                    yield item
            section = new_section
            output = []
        if section:
            for item in _split(output, section):
                yield item    
    def _get_section(s):
        match = re.match(section_regexp, s)
        if not match:
            return
        return match.group(0)
    for section, contents in _group(lines, _get_section):
        yield section, contents

def update_variables(inputlst, variables, indenting=""):
    """Update a list of (key, value) pairs with new variables. Non-used
    variables are preserved. New variables are added at the end of the file."""
    def append_pair(lst, key, value):
        if not isinstance(value, (list, tuple)):
          value = [value]
        for item in value:
          lst.append((key, item))
    dvariables = dict(variables)
    output = []
    seen = set()
    for key, value in inputlst:
        keystripped = key.strip()
        if keystripped:
            # update indenting only if file has some contents
            indenting = re.match("^\s*", key).group(0)
        if keystripped in seen:
            continue
        elif key.strip() in dvariables:
            seen.add(key.strip())
            value = dvariables[keystripped]            
            if value is not None:
                append_pair(output, key, value)
        else:
            append_pair(output, key, value)
    for key, value in variables:
        if key not in seen and value is not None:
            append_pair(output, indenting+key, value)
    return output                                                
        
def update_variables_plain(lines, variables, assignation, output_format,
        indenting="", boolean_string=("off", "on")):
    """Update files on the form KEY=VALUE keeping line indenting. 
    
    - Non-existing keys are appended at the end.
    - Lines with repeated keys are discarded.
    - Assignation string is configurable.
    """    
    def _line(key, value):
        if value is None:
            return key
        elif isinstance(value, bool):
            value = boolean_string[int(value)]
        return output_format % (key.rstrip(), str(value).strip())    
    def _build():
        for line in lines:
            indenting = re.match("^(\s*)", line).group(0)            
            match = re.match(assignation, line)
            if not match:
                key, value = line, None
            else: 
                key, value = match.groups()
                key = indenting+key
            yield key, value     
    for key, value in update_variables(list(_build()), variables, indenting):
        yield _line(key, value)

def update_variables_with_sections(lines0, variables, section_assignation, 
        variable_assignation, output_format, section_indenting="\t",
        section_end=None):
    """Update lines on a sectioned content."""
    def _generator():
        def _getkey(s):
            if s is None:
                return
            return re.match(section_assignation, s).group(1)
        def _build(section_header, contents):
            if section is None:
                contents = map(str.lstrip, contents)
                output = contents
            else: output = section_header + contents
            if section is not None and section_end:
                if output and output[-1].strip() != section_end:
                    output.append(section_end)
            return output
        dvariables = dict((_getkey(k), (k, v)) for (k, v) in variables)
        seen = set()
        for section, lines in split_sections(lines0, section_assignation):
            if section is None and None in seen:
                # Non-named sections should be processed only once.
                yield lines
                continue
            sectionkey = _getkey(section)
            if sectionkey in dvariables:
                new_section, value = dvariables[sectionkey]
                if value is not None and sectionkey not in seen:
                    output = list(update_variables_plain(lines, value,
                        variable_assignation, output_format=output_format))
                    yield _build([new_section], output)
                    seen.add(sectionkey)
            else:
                yield _build([section], lines)

        for section, variables2 in variables:
            if _getkey(section) not in seen and variables2 is not None:
                output = list(update_variables_plain([], variables2,
                    variable_assignation, output_format, section_indenting))
                yield _build(["", section], output)

    # Finally get only strings and join the lines
    output = [x for x in misc.flatten(_generator()) if x is not None]
    return "\n".join(strip_lines(map(str.rstrip, output))) + "\n"

def get_items(items, header, replace, callback=None):
    """Iterate d and return pairs with key starting with a given header."""
    def _generator():
        for k, v in items:
            if k.startswith(header):
                if callback:
                    v = callback(k, v)
                yield (re.sub("^"+replace, "", k), v)
    return list(_generator())
    
def generic_split(s, splitchars=",; "):
    """Get a splitchars-separated string and return stripped values in a list"""
    if not s:
    	return []
    #ver re.split
    return misc.itrue(s2.strip() for s2 in re.split("[%s]" % splitchars, s))

def update_variables_with_sections_testing(lines, variables, section_assignation, 
        section_format, variable_assignation, variable_format, 
        section_indenting="\t", section_end=None):

    def get_line(output, current_variables, key, value):            
        if value is not None:
            if isinstance(value, list):
                if not value:
                    return
                value2 = value.pop(0) 
            else: value2 = value
            new_line = variable_format % (key, value2) 
            remove_key(current_variables, key)
            output.append(indention+new_line)
            
    output = []
    current_section = None
    dvariables = dict(variables)
    indention = ""
    for line in lines:
        if not line.strip():
            output.append(line)
            continue
        section_match = re.match(section_assignation, line)
        variable_match = re.match(variable_assignation, line)
        if section_match:
            # esto hay que hacerlo tambien en variable
            if current_section and current_section in dvariables:
                output = strip_tail(output)
                for key, value in dvariables[current_section]:                    
                    get_line(output, current_variables, key, value)
            if not variable_match:
                new_current_section = section_match.group(0)
                def process(s):
                    m = re.match(section_assignation, s)
                    if m and m.group(1) == section_match.group(1):
                        return s
                match_key = misc.first(process(s) for s in dvariables)
                if match_key:                
                    new_current_section = match_key
                    if dvariables[new_current_section] is not None:
                        output.append(new_current_section)
                current_section = new_current_section                
                continue
        indention = re.match("(\s*)", line).group(1)
        if variable_match:
            key, value = variable_match.groups()
            if current_section and current_section in dvariables:
                current_variables = dvariables[current_section]
                if current_variables is None:
                    continue                                    
                current_dvariables = dict(current_variables)            
            else:
                current_variables = variables
                current_dvariables = dvariables
            if key in current_dvariables:         
                value = current_dvariables[key]
                get_line(output, current_variables, key, value)
            else: 
                output.append(line)
                                    
            
    return output

def remove_key(lst, key):
    return [(k, v) for k, v in lst if k != key]
